package org.timepedia.exporter.doclet; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import com.sun.javadoc.AnnotationDesc; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ConstructorDoc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.ParamTag; import com.sun.javadoc.Parameter; import com.sun.javadoc.RootDoc; import com.sun.javadoc.Tag; import com.sun.javadoc.Type; import com.sun.tools.doclets.formats.html.ConfigurationImpl; import com.sun.tools.doclets.formats.html.HtmlDoclet; import com.sun.tools.doclets.formats.html.HtmlDocletWriter; import com.sun.tools.doclets.internal.toolkit.util.ClassTree; import com.sun.tools.doclets.internal.toolkit.util.DocletConstants; import com.sun.tools.doclets.internal.toolkit.util.PackageListWriter; import com.sun.tools.doclets.internal.toolkit.util.Util; /** * Generates Js and Gss docs. */ public class JsDoclet extends HtmlDoclet { public static boolean start(RootDoc rootDoc) { JsDoclet jsDoclet = new JsDoclet(); try { return jsDoclet.startGeneration3(rootDoc); } catch (Exception e) { e.printStackTrace(); } return false; } private boolean startGeneration3(RootDoc root) throws Exception { configuration = ConfigurationImpl.getInstance(); configuration.root = root; if (root.classes().length == 0) { configuration.message. error("doclet.No_Public_Classes_To_Document"); return false; } configuration.setOptions(); //configuration.getDocletSpecificMsg().notice("doclet.build_version", //configuration.getDocletSpecificBuildDate()); ClassTree classtree = new ClassTree(configuration, configuration.nodeprecated); generateClassFiles(root, classtree); if (configuration.sourcepath != null && configuration.sourcepath.length() > 0) { StringTokenizer pathTokens = new StringTokenizer(configuration.sourcepath, String.valueOf(File.pathSeparatorChar)); boolean first = true; while (pathTokens.hasMoreTokens()) { Util.copyDocFiles(configuration, pathTokens.nextToken() + File.separator, DocletConstants.DOC_FILES_DIR_NAME, first); first = false; } } PackageListWriter.generate(configuration); generatePackageFiles(classtree); generateOtherFiles(root, classtree); configuration.tagletManager.printReport(); return true; } // FIXME: exportOverlay is not implemented yet Map<String,String> classTypeMap = new HashMap<String, String>(); @Override protected void generateOtherFiles(RootDoc rootDoc, ClassTree classTree) throws Exception { super.generateOtherFiles(rootDoc, classTree); HtmlDocletWriter writer = new HtmlDocletWriter(configuration, "jsdoc.html"); writer.html(); writer.head(); writer.println("<meta http-equiv='content-type' content='text/html; charset=UTF-8'/>"); writer.println(getCSS()); writer.headEnd(); writer.body("white", true); ClassDoc[] classes = rootDoc.classes(); Arrays.sort(classes); // Document static methods List<MethodDoc> smethods = new ArrayList<MethodDoc>(); for (ClassDoc clz : classes) { if (isExportable(clz)) { // FIXME: implement exportoverlay somehow. classTypeMap.put(clz.simpleTypeName(), getExportedName(clz, false, true)); if (hasStaticMethods(clz)) { for (MethodDoc md : clz.methods()) { if (md.isStatic() && isExportable(md)) { smethods.add(md); } } } } } if (smethods.size() > 0) { writer.h1("Exported JavaScript-API: Index of static functions"); writer.table(1, "100%", 0, 0); java.util.Collections.sort(smethods); for (MethodDoc md: smethods) { writeMethod(writer, false, true, md); } writer.tableEnd(); } List<ClassDoc> eclasses = new ArrayList<ClassDoc>(); for (ClassDoc clz : classes) { if (isExportable(clz) && hasClassMethods(clz) && !isExportedClosure(clz)) { eclasses.add(clz); } } if (eclasses.size() > 0) { // Write an index of classes writer.h1("Exported JavaScript-API: Index of Classes"); writer.ul(); for (ClassDoc clz : eclasses) { writer.li(); writer.println(getExportedName(clz, true, true)); } writer.ulEnd(); // Write each class for (ClassDoc clz : eclasses) { String className = getExportedName(clz, false, true); writer.h2("<div id=" + className + ">"+ className + "</div>"); String comments = clz.commentText().trim(); if (!comments.isEmpty()) { writer.println("<div class=jsdocText>" + filter(clz.commentText()) + "</div>"); } writer.table(1, "100%", 0, 0); writeConstructors(writer, clz); writeMethods(writer, clz, true, isExportedAll(clz), new ArrayList<String>()); writer.tableEnd(); } } writer.bodyEnd(); writer.htmlEnd(); writer.flush(); writer.close(); } private void writeConstructors(HtmlDocletWriter writer, ClassDoc clz) { String cName = getExportedName(clz, false, true); boolean firstcon = true; for (ConstructorDoc cd : clz.constructors()) { if (isExportable(cd)) { if (firstcon) { writer.tr(); writer.tdColspanBgcolorStyle(2, "", "jsdocHeader"); writer.print("Constructors"); firstcon = false; writer.tdEnd(); writer.trEnd(); } writer.tr(); writer.tdVAlignClass("top", "jsdocRetType"); writer.print(" "); writer.tdEnd(); writer.tdVAlignClass("top", "jsdocMethod"); writer.print("<span class=jsdocMethodName>" + cName + "</span>("); writeParameters(writer, cd.parameters()); writer.print(")"); writer.br(); String comment = filter(cd.commentText()); comment += getCommentTags(cd); writer.print("<span class=jsdocComment>" + comment + "</span>"); writer.tdEnd(); writer.trEnd(); } } } private void writeMethods(HtmlDocletWriter writer, ClassDoc clz, boolean firstcon, boolean all, List<String> visited) { if (clz == null) { return; } for (MethodDoc md : clz.methods()) { if (!md.isStatic() && md.isPublic() && !md.isAbstract()) { String sig = getSignatureMethod(md); if (!visited.contains(sig)) { if (all || isExportable(md)) { writeMethod(writer, firstcon, false, md); firstcon = false; } visited.add(sig); } } } writeMethods(writer, clz.superclass(), firstcon, all, visited); } private MethodDoc getMethodInInterface(MethodDoc md) { if (!md.containingClass().isInterface()) { String sig = getSignatureMethod(md); for (ClassDoc cd: md.containingClass().interfaces()) { for (MethodDoc m: cd.methods()) { if (sig.equals(getSignatureMethod(m)) && isExportable(m)) { return m; } } } } return null; } private MethodDoc getMethodInSuperclass(ClassDoc superc, MethodDoc md) { if (superc != null) { String sig = getSignatureMethod(md); for (MethodDoc m : superc.methods()) { if (sig.equals(getSignatureMethod(m))) { return m; } } return getMethodInSuperclass(superc.superclass(), md); } return null; } private String getSignatureMethod (MethodDoc md) { return md.returnType() + " " + md.name() + md.signature(); } private void writeMethod(HtmlDocletWriter writer, boolean firstcon, boolean writePakage, MethodDoc md) { ClassDoc cd = md.containingClass(); String pkg = writePakage ? getExportedName(cd, false, true) : ""; String name = getExportedName(md); if (name.startsWith("$wnd")) { pkg = ""; name = name.replaceFirst("^\\$wnd\\.", ""); } if (!pkg.isEmpty()) { pkg += "."; } if (firstcon) { writer.tr(); writer.tdColspanBgcolorStyle(2, "", "jsdocHeader"); writer.print("Methods"); firstcon = false; writer.tdEnd(); writer.trEnd(); } writer.tr(); writer.tdVAlignClass("top", "jsdocRetType"); writer.print(getExportedName(md.returnType(), true)); writer.tdEnd(); writer.tdVAlignClass("top", "jsdocMethod"); writer.print( "<b class=jsdocMethodName>" + pkg + name + "</b>" + "("); writeParameters(writer, md.parameters()); writer.print(")"); writer.br(); String comments = md.commentText(); if (!md.isStatic() && comments.isEmpty()) { MethodDoc id = getMethodInInterface(md); if (id != null) { comments = id.commentText(); } if (comments.isEmpty()) { id = getMethodInSuperclass(cd.superclass(), md); } else { md = id; } if (id != null) { comments = id.commentText(); md = id; } } comments += getCommentTags(md); if (!comments.isEmpty()) { writer.print("<span class=jsdocComment>" + filter(comments) + "</span>"); } writer.tdEnd(); writer.trEnd(); } private String getCommentTags(ExecutableMemberDoc doc) { String ret = ""; if (doc != null) { ret += "<ul class='params'>"; for (ParamTag t : doc.paramTags() ){ ret +="<li><span class='paramName'>" + t.parameterName() + "</span> " + t.parameterComment(); } for (Tag t : doc.tags("return") ){ ret += "<li><span class='return'>return</span> " + t.text(); } ret += "</ul>"; } return ret; } private boolean hasClassMethods(ClassDoc clz) { if (clz != null && !clz.isInterface() && !clz.isAbstract()) { for (ConstructorDoc cd : clz.constructors()) { if (isExportable(cd)) { return true; } } for (MethodDoc md : clz.methods()) { if (!md.isStatic()) { boolean exportable = isExportable(md); if (!exportable && (md = getMethodInInterface(md)) != null) { exportable = isExportable(md); } if (exportable) { return true; } } } return hasClassMethods(clz.superclass()); } return false; } private boolean hasStaticMethods(ClassDoc clz) { int countExportedMethods = 0; for (MethodDoc md : clz.methods()) { if (isExportable(md) && md.isStatic()) { countExportedMethods++; } } return countExportedMethods > 0; } private String getExportedName(MethodDoc cd) { String ename = cd.name(); for (AnnotationDesc a : cd.annotations()) { if (a.annotationType().name().equals("Export")) { for (AnnotationDesc.ElementValuePair p : a.elementValues()) { ename = p.value().toString().trim(); break; } } } return ename.replaceAll("\"", ""); } protected String filter(String s) { if (s.startsWith("Created")) { return ""; } s = s.replaceAll("(?s)\\{@link\\s[^\\}]*?#(.+)\\}", "$1"); s = s.replaceAll("(?s)\\{@link\\s[^\\}]*?([^\\.\\}]+)\\}", "<a href=#$1>$1</a>"); return s; } private String getExportedName(Type clz, boolean link) { return (clz.isPrimitive() ? "void".equals(clz.typeName()) ? " " : clz.typeName() : getExportedName(clz.asClassDoc(), link, false)) + clz.dimension(); } private void writeParameters(HtmlDocletWriter writer, Parameter[] ps) { writer.print(getParameterString(ps)); } private boolean isExportable(ConstructorDoc cd) { boolean export = isExported(cd.containingClass()); for (AnnotationDesc a : cd.annotations()) { if (a.annotationType().name().equals("Export")) { export = true; } if (a.annotationType().name().equals("NoExport")) { export = false; } } return export; } private boolean isExportable(MethodDoc md) { if (md == null) { return false; } boolean export = isExported(md.containingClass()); for (AnnotationDesc a : md.annotations()) { if (a.annotationType().name().equals("Export")) { return true; } if (a.annotationType().name().equals("NoExport")) { return false; } } if (!export) { export = isExportable(getMethodInInterface(md)); } return export; } private boolean isExportedClosure(ClassDoc clz) { for (AnnotationDesc a : clz.annotations()) { String aname = a.annotationType().name(); if (aname.equals("ExportClosure")) { return true; } } return false; } private boolean isExportedAll(ClassDoc clz) { for (AnnotationDesc a : clz.annotations()) { String aname = a.annotationType().name(); if (aname.equals("Export") || aname.equals("ExportClosure")) { for (AnnotationDesc.ElementValuePair p : a.elementValues()) { if ("all".equals(p.element().name())) { return "true".equals(p.value().toString()); } } } } return false; } private boolean isExported(ClassDoc clz) { for (AnnotationDesc a : clz.annotations()) { String aname = a.annotationType().name(); if (aname.equals("Export") || aname.equals("ExportClosure")) { return true; } } return false; } private String getExportedPackage(ClassDoc clz) { if (clz == null) { return ""; } PackageDoc cpkg = clz.containingPackage(); String pkg = cpkg == null ? "" : (cpkg.name().trim()); for (AnnotationDesc a : clz.annotations()) { if (a.annotationType().name().equals("ExportPackage")) { for (AnnotationDesc.ElementValuePair p : a.elementValues()) { pkg = p.value().toString().replaceAll("\"", ""); if (!pkg.isEmpty()) { pkg += "."; } break; } } } return pkg; } private String getExportedName(ClassDoc clz, boolean withLink, boolean withPkg) { if (clz == null) { return ""; } PackageDoc cpkg = clz.containingPackage(); String pkg = cpkg == null ? "" : cpkg.name(); String name = clz.name(); boolean isClosure = false; boolean isEnclosed = clz.containingClass() != null; boolean isExportEnclosed = isEnclosed && isExportable(clz.containingClass()); boolean removeEnclosedPart = false; if (isExportEnclosed) { pkg = getExportedName(clz.containingClass(), false, true); removeEnclosedPart = true; } for (AnnotationDesc a : clz.annotations()) { if (a.annotationType().name().equals("ExportPackage")) { for (AnnotationDesc.ElementValuePair p : a.elementValues()) { pkg = p.value().toString().trim(); removeEnclosedPart = true; break; } } if (a.annotationType().name().equals("Export")) { for (AnnotationDesc.ElementValuePair p : a.elementValues()) { if ("value".equals(p.element().name())) { name = p.value().toString().trim().replaceAll("\"", ""); if (!name.isEmpty()) { removeEnclosedPart = false; break; } } } } if (a.annotationType().name().equals("ExportClosure")) { isClosure = true; name = "<i class=jsdocClosureFunc>function</i>("; name += getParameterString(clz.methods()[0].parameters()); name += ")"; pkg = ""; } } if (removeEnclosedPart) { name = name.replaceFirst("^.+\\.", ""); } pkg = pkg.replaceAll("\"", "").replaceFirst("\\.+$", ""); if (!pkg.isEmpty()) { pkg += "."; } if (withLink && !isClosure && !name.matches("String|JavaScriptObject|Object|Exportable|Class|Date")) { if (withPkg) { name = pkg + "<a href=#" + pkg + name + "><b>" + name + "</b></a>"; } else { name = "<a href=#" + pkg + name + ">" + name + "</a>"; } } else if (withPkg) { name = pkg + name; } return name; } private String getParameterString(Parameter[] ps) { String result = ""; for (int i = 0; i < ps.length; i++) { Type type = ps[i].type(); String ename = getExportedName(type, true); String pname = ename.contains("function") ? "{}" : ps[i].name(); result += "<span class=jsdocParameterType>" + ename + "</span> <span class=jsdocParameterName>" + pname + "</span>"; if (i < ps.length - 1) { result += ", "; } } return result; } private static boolean isExportable(ClassDoc clz) { if (clz == null) { return false; } for (ClassDoc i : clz.interfaces()) { if (i.name().contains("Exportable")) { return true; } for (ClassDoc j : i.interfaces()) { if (j.name().contains("Exportable")) { return true; } } } for (MethodDoc m : clz.methods()) { if (!m.isStatic()) { for (AnnotationDesc a : m.annotations()) { if (a.annotationType().name().equals("Export")) { return true; } } } } return isExportable(clz.superclass()); } public String getCSS() { return "<style>" + " body,table {" + " background-color: #f3ead8;" + " color: #000000;" + " font-family: Arial, Helvetica, sans-serif;" + " font-size: 14px;" + " }" + " h1,h2 {" + " font-family: Trebuchet MS;" + " color: navy;" + " font-size: 18pt;" + " border-bottom: double 3px;" + " padding-top: 20px;" + " padding-left: 20px;" + " }" + " table {" + " width: 100%;" + " border: 1px;" + " }" + " td.jsdocRetType {" + " width: 200px;" + " }" + " div.jsdocText {" + " padding-bottom: 20px;" + " }" + " .jsdocHeader {" + " background-color: #E8F1F6;" + " padding: 5px;" + " font-weight: bold;" + " }" + " td {" + " padding-left: 5px;" + " padding-right: 10px;" + " }" + " .jsdocMethodName {" + " font-weight: bold;" + " }" + " .jsdocComment,.jsdocComment p {" + " font-size: 11px;" + " color: blue;" + " padding-left: 15px;" + " }" + " a {" + " color: #331166;" + " text-decoration: none;" + " }" + " a:hover {" + " color: #666666;" + " text-decoration: underline;" + " }" + " .params li {" + " }" + " .paramName, .return {" + " font-weight: bold;" + " }" + "</style>"; } }